﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Nina.Codec;

namespace Nina
{
    /// <summary>
    /// Represents a network I/O service
    /// </summary>
    public class NinaService
    {
        protected List<NinaCodec> m_codecs;
        protected NinaProtocolHandler m_protocolHandler;

        /// <summary>
        /// Constructor
        /// </summary>
        public NinaService()
        {
            m_codecs = new List<NinaCodec>();
            m_protocolHandler = null;
        }

        /// <summary>
        /// Processes a socket's I/O
        /// </summary>
        /// <param name="state">The NinaSession object</param>
        protected void ProcessSession(object state)
        {
            NinaSession session = (NinaSession)state;

            /* If the session is closed, end the method */
            //if (!session.IsConnected)
            //{
            //    /* Notify the library layer */
            //    foreach (NinaCodec codec in m_codecs)
            //    {
            //        codec.OnSessionClosed(session);
            //    }

            //    /* Notify the application layer */
            //    m_protocolHandler.SessionClosed(session);
            //    return;
            //}

            /* If there is information to read, process it */
            if (session.CanRead)
            {
                byte[] data = session.Read();
                if (data != null)
                {
                    byte[] result = data;
                    byte[] remaining = null;
                    while (result.Length > 0)
                    {
                        bool success = true;
                        /* Process the data through all the codecs */
                        foreach (NinaCodec codec in m_codecs)
                        {
                            if (!codec.OnRead(session, ref result, ref remaining))
                            {
                                /* 
                                 * If an incomplete message occurred, 
                                 * store it for later and break out of the loop
                                 */
                                session.ReadBuffer.AddRange(remaining);
                                success = false;
                                break;
                            }
                        }

                        if (success)
                        {
                            /* Successful message read, notify application and continue looping */
                            m_protocolHandler.MessageReceived(session, result);
                            result = remaining;
                        }
                        else
                        {
                            /* Failed read, data has been stored for later - end the loop */
                            break;
                        }
                    }
                }
            }

            /* If there is data queued for writing, process it */
            {
                byte[] data = null;
                while (session.CanWrite && session.IsConnected)
                {
                    data = session.WriteBuffer[0];
                    byte[] result = data;
                    /* Process the data through codecs */
                    for (int i = m_codecs.Count - 1; i >= 0; i--)
                    {
                        NinaCodec codec = m_codecs[i];
                        result = codec.OnWrite(session, result);
                    }
                    
                    /* Attempt to flush data, remove from queue on success */
                    if (session.Flush(result))
                    {
                        session.WriteBuffer.RemoveAt(0);
                    }
                }
            }

            /* Sleep briefly to reduce CPU usage */
            try
            {
                Thread.Sleep(1);
            }
            catch { }

            /* Requeue the session for processing */
            ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessSession), session);
        }

        /// <summary>
        /// Sets the protocol handler used by the service
        /// </summary>
        public void SetProtocolHandler(NinaProtocolHandler handler)
        {
            m_protocolHandler = handler;
        }

        /// <summary>
        /// Returns the protocol handler used by the service
        /// </summary>
        public NinaProtocolHandler GetProtocolHandler()
        {
            return m_protocolHandler;
        }

        /// <summary>
        /// Adds a codec to the start of the codec list
        /// </summary>
        public void AddCodecFirst(NinaCodec codec)
        {
            m_codecs.Insert(0, codec);
        }

        /// <summary>
        /// Adds a codec to the end of the codec list
        /// </summary>
        public void AddCodecLast(NinaCodec codec)
        {
            m_codecs.Add(codec);
        }

        /// <summary>
        /// Removes a codec from the codec list
        /// </summary>
        public void RemoveCodec(NinaCodec codec)
        {
            m_codecs.Remove(codec);
        }
    }
}
